﻿namespace Aditi.Security.InputStringFilter
{
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Text.RegularExpressions;

    public static class InputStringFilter
    {
        #region Methods

        public static bool ContainsAlpha(this string str)
        {
            if (String.IsNullOrEmpty(str))
                throw new ArgumentException("Evaluation string can not be empty", str);
            return str.ContainsPattern("alpha");
        }

        public static bool ContainsAlphaWithCase(this string str)
        {
            if (String.IsNullOrEmpty(str))
                throw new ArgumentException("Evaluation string can not be empty", str);
            return str.ContainsPattern("alphawithcase");
        }

        /// <summary>
        /// Determines whether provided string matches Filters defined in group named alphatogamma.
        /// </summary>
        /// <param name="str">The string to be evaluated.</param>
        /// <returns>
        /// 	<c>true</c> if the specified string matches filters defined in group named alphatogamma; otherwise, <c>false</c>.
        /// </returns>
        public static bool ContainsGroupAlphaToGamma(this string str)
        {
            return str.ContainsPatternGroup("alphatogamma");
        }

        /// <summary>
        /// Determines whether the specified string contains pattern specified in the configuration file with the provided name.
        /// </summary>
        /// <param name="str">The string to be evaluated.</param>
        /// <param name="inputPatternName">Name of the input pattern.as defined in configuration file</param>
        /// <returns>
        /// 	<c>true</c> if the specified string contains pattern; otherwise, <c>false</c>.
        /// </returns>
        public static bool ContainsPattern(this string str, string inputPatternName)
        {
            if (String.IsNullOrEmpty(str))
                throw new ArgumentException("Evaluation string can not be empty", str);
            if (string.IsNullOrEmpty(inputPatternName))
                throw new ArgumentException("Name of the input pattern can not be empty", inputPatternName);
            Filter filter = Configuration.inputFilter.GetFilter(inputPatternName);
            if (filter.IsRegex)
                return str.ContainsPatternRegex(filter.Pattern, false, !filter.IsCaseSensitive);
            else
            {
                string filterPattern = filter.Pattern;
                if (!filter.IsCaseSensitive)
                {
                    str = str.ToUpper();
                    filterPattern = filterPattern.ToUpper();
                }
                return str.ContainsArbitraryPattern(filterPattern);
            }
        }

        /// <summary>
        /// Determines whether the string contains any of the filters defined in Group with the specified name.
        /// </summary>
        /// <param name="str">The string to be processed.</param>
        /// <param name="inputPatternGroupName">Name of the input pattern group.</param>
        /// <returns>
        /// 	<c>true</c> if the string matches any of the patterns in the group; otherwise, <c>false</c>.
        /// </returns>
        public static bool ContainsPatternGroup(this string str, string inputPatternGroupName)
        {
            if (String.IsNullOrEmpty(str))
                throw new ArgumentException("Evaluation string can not be empty", str);
            if (String.IsNullOrEmpty(inputPatternGroupName))
                throw new ArgumentException("Name of the input pattern can not be empty", inputPatternGroupName);
            return Configuration.inputFilter.Groups[inputPatternGroupName].Any(x => str.ContainsPattern(x.Name));
        }

        /// <summary>
        /// Determines whether the provided string evaluates true to the provided regular expression.
        /// </summary>
        /// <param name="str">The string to be evaluated.</param>
        /// <param name="regexString">The regex string.</param>
        /// <param name="isSingleLine">if set to <c>true</c> regex is evaluated singleline. This optimizes regex performance</param>
        /// <param name="caseSensitive">if set to <c>true</c> regex is evaluated with case sensitivity. This impacts regex performance.</param>
        /// <returns>
        /// 	<c>true</c> if provided string matches the provided regex; otherwise, <c>false</c>.
        /// </returns>
        public static bool ContainsPatternRegex(this string str, string regexString, bool isSingleLine, bool caseSensitive)
        {
            if (string.IsNullOrEmpty(str))
                throw new ArgumentException("Evaluation string can not be empty", str);
            if (string.IsNullOrEmpty(regexString))
                throw new ArgumentException("The Regex expression can not be empty", str);
            Regex regex = new Regex(regexString,
                RegexOptions.Compiled |
                ((isSingleLine) ? RegexOptions.Singleline : RegexOptions.Multiline) |
                ((!caseSensitive) ? RegexOptions.IgnoreCase : RegexOptions.None));
            return regex.IsMatch(str);
        }

        /// <summary>
        /// Replaces the pattern defined with name alpha with configured replacement pattern.
        /// </summary>
        /// <param name="str">The string to be evaluated.</param>
        /// <returns>The new string with replacement</returns>
        public static string ReplaceAlpha(this string str)
        {
            return str.ReplacePattern("alpha");
        }

        /// <summary>
        /// Replaces the pattern.
        /// </summary>
        /// <param name="str">The STR.</param>
        /// <param name="inputFilterName">Name of the input filter.</param>
        /// <returns></returns>
        public static string ReplacePattern(this string str, string inputFilterName)
        {
            if (!str.ContainsPattern(inputFilterName))
                return str;
            Filter filter = Configuration.inputFilter.GetFilter(inputFilterName);
            return str.ReplacePattern(filter);
        }

        /// <summary>
        /// Replaces the pattern.
        /// </summary>
        /// <param name="str">The STR.</param>
        /// <param name="oldValue">The old value.</param>
        /// <param name="newValue">The new value.</param>
        /// <param name="comparison">The comparison.</param>
        /// <returns></returns>
        /// <remarks>http://stackoverflow.com/questions/244531/is-there-an-alternative-to-string-replace-that-is-case-insensitive</remarks>
        public static string ReplacePattern(this string str, string oldValue, string newValue, StringComparison comparison)
        {
            StringBuilder sb = new StringBuilder();

            int previousIndex = 0;
            int index = str.IndexOf(oldValue, comparison);
            while (index != -1)
            {
                sb.Append(str.Substring(previousIndex, index - previousIndex));
                sb.Append(newValue);
                index += oldValue.Length;

                previousIndex = index;
                index = str.IndexOf(oldValue, index, comparison);
            }
            sb.Append(str.Substring(previousIndex));

            return sb.ToString();
        }

        /// <summary>
        /// Replaces the script tag.
        /// </summary>
        /// <param name="str">The STR.</param>
        /// <returns></returns>
        public static string ReplaceScript(this string str)
        {
            return str.ReplacePattern("detectjavascript");
        }

        /// <summary>
        /// Determines whether [contains arbitrary pattern] [the specified STR].
        /// </summary>
        /// <param name="str">The STR.</param>
        /// <param name="filterString">The filter string.</param>
        /// <returns></returns>
        private static bool ContainsArbitraryPattern(this string str, string filterString)
        {
            return str.Contains(filterString);
        }

        /// <summary>
        /// Replaces the pattern.
        /// </summary>
        /// <param name="str">The STR.</param>
        /// <param name="filter">The filter.</param>
        /// <returns></returns>
        private static string ReplacePattern(this string str, Filter filter)
        {
            if (filter.IsRegex)
            {
                RegexOptions rxOption = (!filter.IsCaseSensitive) ? RegexOptions.IgnoreCase : RegexOptions.None;
                return Regex.Replace(str, filter.Pattern, filter.Replacement, rxOption);
            }
            else
            {
                StringComparison strComparison = (!filter.IsCaseSensitive) ? StringComparison.OrdinalIgnoreCase : StringComparison.Ordinal;
                //todo: Benchmark both Pattern Replacement algorithms.
                return str.ReplacePattern(filter.Pattern, filter.Replacement, strComparison);
            }
        }

        /// <summary>
        /// Replaces the ex.
        /// </summary>
        /// <param name="str">The STR.</param>
        /// <param name="original">The original.</param>
        /// <param name="pattern">The pattern.</param>
        /// <param name="replacement">The replacement.</param>
        /// <returns></returns>
        /// <remarks>http://www.codeproject.com/KB/string/fastestcscaseinsstringrep.aspx</remarks>
        private static string ReplacePattern(this string str, string original, string pattern, string replacement)
        {
            int count, position0, position1;
            count = position0 = position1 = 0;
            string upperString = original.ToUpper();
            string upperPattern = pattern.ToUpper();
            int inc = (original.Length / pattern.Length) *
                      (replacement.Length - pattern.Length);
            char[] chars = new char[original.Length + Math.Max(0, inc)];
            while ((position1 = upperString.IndexOf(upperPattern,
                                              position0)) != -1)
            {
                for (int i = position0; i < position1; ++i)
                    chars[count++] = original[i];
                for (int i = 0; i < replacement.Length; ++i)
                    chars[count++] = replacement[i];
                position0 = position1 + pattern.Length;
            }
            if (position0 == 0) return original;
            for (int i = position0; i < original.Length; ++i)
                chars[count++] = original[i];
            return new string(chars, 0, count);
        }

        #endregion Methods
    }
}